﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Structures;

namespace X3DXamlXna
{
    public interface KDObjects
    {
        BoundingBox _Box { get; }
        bool _isLeaf { get; }
        KDObjects left { get; }
        KDObjects right { get; }
        List<X3dModel> Models { get; }
    }

    class KDNode : KDObjects
    {
        public BoundingBox _Box { get; set; }
        public bool _isLeaf { get { return false; } private set { } }
        public KDObjects left { get; set; }
        public KDObjects right { get; set; }
        public List<X3dModel> Models { get; set; }

        public KDNode(IEnumerable<X3dModel> models)
        {
            Models = new List<X3dModel>();
            List<Vector3> pts = new List<Vector3>();
            //estraggo tutti i vertici dai modelli per costruire la boundingbox del nodo
            foreach (X3dModel elem in models)
            {
                var tmp = (from verts in elem.Vertices
                           let trans = Vector3.Transform(verts.Position, elem.World)
                           select trans).ToList();
                pts.AddRange(tmp);
            }

            _Box = BoundingBox.CreateFromPoints(pts);

            IEnumerable<X3dModel> maybeLeftPoint;
            IEnumerable<X3dModel> maybeRightPoint;
            //rimando a split i lavori "sporchi" in maniera tale da mantenere la chieraezza della classe nodo e delle sue 
            //principali funzionalita facendomi concentrare piu' sulle operazioni tra modelli
            split(models, out maybeLeftPoint, out maybeRightPoint);
            //carico in upper model i modeli che sono presenti in piu' bounding nello stesso momento
            //in maniera tale che verranno sempre caricati a prescindere della posizione in cui mitrovo nelle
            //sotto bounding
            //uppermodel mi rappresenta il modello dato dall'intersezione delle liste di modelli del sottoramo destro e sinistro
            //dandomi come risultato il modello/modelli che si trovano in entrambi i sottoalberi
            //evito di mandarlo nei livelli inferiori ma lo tengo in questo livello diminuendo (in alcuni casi la profondita' del
            //albero che cmq rimane O(log n))
            Models = maybeLeftPoint.Intersect(maybeRightPoint).ToList();
            //li salvo
            //if (Models.Count != 0)
            //{
            //    Label = "parts\\" + this.GetHashCode() + ".part";
            //    Stream str = File.Open(Label, FileMode.Create);
            //    BinaryFormatter bf = new BinaryFormatter();
            //    bf.Serialize(str, models);
            //    str.Close();
            //    empty = false;
            //}
            //else empty = true;

            //estraggo ora il modello intersecato perche' non ha senso rimandarlo nei sottoalberi
            var leftModels = maybeLeftPoint.Except(Models).ToList();
            var rightModels = maybeRightPoint.Except(Models).ToList();
            //se ho raggiunto un numero di vertici minimo per formare una foglia
            if (pts.Count() <= Consts.POINT_FOR_LEAF)
            {
                //utilizzando questo metodo cioe' salvare i modelli anche a livello di foglia capita in alcuni casi particolari
                //di trovarci ad avere che esempio 2 modelli model 1 e model 2 ma model 1 era contenuto sia in nel primo che 
                //nel secondo sottoalbero quindi rimarra' a livello di nodo quindi il suo sottoalbero destro sara' vuoto 
                //mentre il sottoalbero sinistro conterra' model 2
                //OTTIMIZZABILE alle foglie con 0 modelli vengono assegnate le bounding del nodo padre per comodita'
                //altrimenti il metodo dell'intersezione in Utils dava problemi
                //quindi meglio fare controlli sul Count dei modelli nelle foglie SEMPRE oppure farli fare un controllo 
                //delle intersezione in piu' ogni tanto???
                if (leftModels.Count == 0)
                    left = new KDLeaf(_Box);
                else
                    left = new KDLeaf(leftModels);
                if (rightModels.Count == 0)
                    right = new KDLeaf(_Box);
                else
                    right = new KDLeaf(rightModels);
            }

            else
            {
                if (leftModels.Count == 0)
                    left = new KDLeaf(_Box);
                else
                    left = new KDNode(leftModels);
                if (rightModels.Count == 0)
                    right = new KDLeaf(_Box);
                else
                    right = new KDNode(rightModels);
            }
        }

        private void split(IEnumerable<X3dModel> models, out IEnumerable<X3dModel> left, out IEnumerable<X3dModel> right)
        {
            var vector3Models = new List<Vec3Model>();
            foreach (X3dModel elem in models)
            {
                //creo la tupla (vector3,X3dModel) i maniera tale da non perdere il riferimenti:
                //a quale modello appartiene quel determinato vertice questo risultera utile nella fase di split
                //perche' al ritorno di questa funzione mi bastera estrarre la lista di modelli da tmpLeftModels e 
                //maybeRightPoint effettuare un distinct su ciascuno di questi ed infine intersecarli tra di loro
                //ottenendo come risultato il modello/modelli che si trovano in piu bouding nello stesso albero
                //quindi salvandolo nel nodo invece di spezzettarlo perdendo precisione nel disegnare gli oggetti visibili                                
                vector3Models.AddRange(Utils.buildVec3Model(elem));
            }


            int howMany = vector3Models.Count();

            IEnumerable<Vec3Model> opts;

            IEnumerable<float> xs = (from elem in vector3Models select elem.Coord.X).ToArray();
            IEnumerable<float> ys = (from elem in vector3Models select elem.Coord.Y).ToArray();
            IEnumerable<float> zs = (from elem in vector3Models select elem.Coord.Z).ToArray();

            float vx = Utils.Variance(xs);
            float vy = Utils.Variance(ys);
            float vz = Utils.Variance(zs);

            if (vx >= vy && vx >= vz)
            {
                opts = vector3Models.OrderBy(g => g.Coord.X);

            }
            else if (vy >= vx && vy >= vz)
            {
                opts = vector3Models.OrderBy(g => g.Coord.Y);

            }
            else
            {
                opts = vector3Models.OrderBy(g => g.Coord.Z);
            }
            left = (from elem in opts.Take(howMany / 2) select elem.Model).Distinct().ToList();
            right = (from elem in opts.Skip(howMany / 2) select elem.Model).Distinct().ToList();
        }




        //public void LoadContent(GraphicsDevice graphics)
        //{
        //    if (!empty && Label.Length != 0)
        //    {
        //        Stream str = File.Open("parts\\" + this.GetHashCode() + ".part", FileMode.Open);
        //        BinaryFormatter serializer = new BinaryFormatter();
        //        Models = serializer.Deserialize(str) as List<X3dModel>;
        //        str.Close();
        //        foreach (var model in Models)
        //        {
        //            model.BuildIndexBuffer(graphics);
        //            model.BuildVertexBuffer(graphics);
        //        }


        //    }

        //}

    }
    class KDLeaf : KDObjects
    {

        public BoundingBox _Box { get; set; }
        public bool _isLeaf { get { return true; } private set { } }
        public List<X3dModel> Models { get; set; }
        public KDObjects left { get { return null; } private set { } }
        public KDObjects right { get { return null; } private set { } }

        public KDLeaf(BoundingBox bbox)
        {
            _Box = bbox;
            this.Models = new List<X3dModel>();
        }
        public KDLeaf(IEnumerable<X3dModel> models)
        {
            List<Vector3> pts = new List<Vector3>();
            foreach (X3dModel elem in models)
            {
                var tmp = (from vts in elem.Vertices
                           select Vector3.Transform(vts.Position, elem.World)).ToList();
                pts.AddRange(tmp);
            }
            _Box = BoundingBox.CreateFromPoints(pts);
            this.Models = new List<X3dModel>();
            this.Models = models.ToList();
            //Serializzazione
            //Label = "";
            //if (models.Count() != 0)
            //{
            //    Label = "parts\\" + this.GetHashCode() + ".part";
            //    Stream str = File.Open(Label, FileMode.Create);
            //    BinaryFormatter bf = new BinaryFormatter();
            //    bf.Serialize(str, models);
            //    str.Close();
            //    empty = false;
            //}
            //else empty = true;
            //this.Models.Clear();
        }

        //Caricamento
        //public void LoadContent(GraphicsDevice graphics)
        //{
        //    if (!empty && Label.Length != 0)
        //    {
        //        Stream str = File.Open("parts\\" + this.GetHashCode() + ".part", FileMode.Open);
        //        BinaryFormatter serializer = new BinaryFormatter();
        //        Models = serializer.Deserialize(str) as List<X3dModel>;
        //        str.Close();
        //        foreach (var model in Models)
        //        {
        //            model.BuildIndexBuffer(graphics);
        //            model.BuildVertexBuffer(graphics);
        //        }
        //    }

        //}
    }
}
